using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using Raven.Abstractions.Data;
using Raven.Abstractions.Indexing;
using Raven.Client;
using Raven.Client.Indexes;
using Raven.Client.Linq;

namespace OpenWaves.ActivityFeed.RavenDB
{
    public class ActivityFeedService : IActivityFeedService
    {
        private readonly IDocumentSessionProvider sessionProvider;

        public ActivityFeedService() : this(ServiceLocator.Resolve<IDocumentSessionProvider>())
        {
        }

        public ActivityFeedService(IDocumentSessionProvider sessionProvider)
        {
            Contract.Requires<ArgumentNullException>(sessionProvider != null);
            
            this.sessionProvider = sessionProvider;
        }

        public ActivityFeedService(IDocumentSession session)
        {
            Contract.Requires<ArgumentNullException>(session != null);
            this.sessionProvider = new DocumentSessionWrapper(session);
        }

        public void Subscribe(string subscriberId, IEnumerable<Topic> topics)
        {           
            var subscriber = this.EnsureSubscriber(subscriberId);
            subscriber.SubscribeToTopics(topics);
        }

        public void Unsubscribe(string subscriberId, IEnumerable<Topic> topics)
        {
            var subscriber = this.sessionProvider.DocumentSession.Load<Subscriber>(subscriberId);
            Contract.Assert(subscriber != null, "Cannot unsubscribe from topics. Subscriber does not exist.");

            subscriber.UnsubscribeFromTopics(topics);
        }

        public IQueryable<Subscriber> GetSubscribers()
        {
            return this.sessionProvider.DocumentSession.Query<Subscriber>();
        }

        public Subscriber GetSubscriber(string subscriberId)
        {
            var subscriber = this.sessionProvider.DocumentSession.Load<Subscriber>(subscriberId);
            if ( subscriber == null)
            {
                return new Subscriber(subscriberId);
            }
            return subscriber;
        }

        public IQueryable<Update> GetUpdates()
        {
            return this.sessionProvider.DocumentSession.Query<Update>().OrderByDescending(u=>u.PublishedOn);
        }

        public IQueryable<Update> SearchUpdates(string searchTerms = null)
        {
            return this.sessionProvider.DocumentSession
                .Query<UpdateFullTextSearchIndex.Result, UpdateFullTextSearchIndex>()
                .Search(u => u.FullText, searchTerms ?? "", 1, SearchOptions.And)
                .OrderByDescending(u=>u.PublishedOn)
                .As<Update>();
        }

        public Update GetUpdate(string updateId)
        {
            return this.sessionProvider.DocumentSession.Load<Update>(updateId);
        }

        /// <inheritdoc/>
        public Update PublishUpdate(UpdateContent content, string publisherId, string source, IEnumerable<Topic> topics, bool isPublic, IEnumerable<UserGroup> userGroups)
        {
            var statusUpdate = new Update(content, publisherId, source, topics, isPublic, userGroups);
            this.sessionProvider.DocumentSession.Store(statusUpdate);
            return statusUpdate;
        }

        public void RemoveUpdate(Update update)
        {
            this.sessionProvider.DocumentSession.Delete(update);
        }

        public void RemoveUpdatesOlderThan(DateTime dateTime)
        {
            var deleteQuery = sessionProvider.DocumentSession.Advanced.LuceneQuery<Update>(
                typeof (UpdatePublishedOnIndex).Name).WhereLessThan(u => u.PublishedOn, dateTime);

            this.sessionProvider.DocumentSession.Advanced.DocumentStore.DatabaseCommands.DeleteByIndex(typeof(UpdatePublishedOnIndex).Name, new IndexQuery()
                {
                    Query = deleteQuery.ToString()
                }, allowStale: true);           
        }

        private Subscriber EnsureSubscriber(string subscriberId)
        {
            var subscriber = this.sessionProvider.DocumentSession.Load<Subscriber>(subscriberId);
            if (subscriber == null)
            {
                subscriber = new Subscriber(subscriberId);
                this.sessionProvider.DocumentSession.Store(subscriber);
            }
            return subscriber;
        }
    }

    

    public class UpdateFullTextSearchIndex : AbstractIndexCreationTask<Update, UpdateFullTextSearchIndex.Result>
    {
        public class Result
        {
            public string Id { get; set; }
            public DateTime PublishedOn { get; set; }
            public string PublishedBy { get; set; }
            public IEnumerable<string> Topics { get; set; }
            public IEnumerable<string> UserGroups { get; set; }
            public bool IsPublic { get; set; }
            public object FullText { get; set; }
        }

        public const string WorkableMap = @"docs.Updates
	                .Select(u => new {Id = u.__document_id, PublishedOn = u.PublishedOn, PublishedBy = u.PublishedBy, Topics = u.Topics, UserGroups = u.UserGroups, IsPublic = u.IsPublic, FullText = new System.Object []{u.Content
	                .Select(x => x.Value), u.Comments
	                .Select(c => c.Text)}})";

#if !NET_40_OR_GREATER
        public override IndexDefinition CreateIndexDefinition()
        {
            var indexDefinition = base.CreateIndexDefinition();
            indexDefinition.Map = WorkableMap;
            return indexDefinition;
        }
#endif

        public UpdateFullTextSearchIndex()
        {
            Map = updates => from u in updates select new
                             {
                                 u.Id, 
                                 u.PublishedOn,
                                 u.PublishedBy, 
                                 u.Topics, 
                                 u.UserGroups, 
                                 u.IsPublic, 
#if NET_40_OR_GREATER
                                 FullText = new object[] { AsDocument(u.Content).Select(x => x.Value), u.Comments.Select(c => c.Text)}
#else
                                 FullText = ""
#endif
                             };
            Index("FullText", FieldIndexing.Analyzed);
            Store("FullText", FieldStorage.No);
            
        }
    }

    public class UpdatePublishedOnIndex: AbstractIndexCreationTask<Update>
    {
        public UpdatePublishedOnIndex()
        {
            Map = updates => from update in updates
                             select new {update.PublishedOn};
        }
    }
}
